/*
    a01_hellowWorld - derived from basicwin in Xlib Programming Manual. 
*/
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "helloicon.h"

#define BITMAPDEPTH 1
#define TOO_SMALL 0
#define BIG_ENOUGH 1

Display *display;
int screen_num;


static void getGC(Window win, GC *gc, XFontStruct *font_info)
{
	unsigned long valuemask = 0; //ignore XGCvalues and use defaults
	XGCValues values;
	unsigned int line_width = 6;
	int line_style = LineSolid; //LineOnOffDash;
	int cap_style = CapRound;
	int join_style = JoinRound;
	int dash_offset = 0;
	static char dash_list[] = {12, 24};
	int list_length = 2;

	/* Create default Graphics Context */
	*gc = XCreateGC(display, win, valuemask, &values);

	/* specify font */
	XSetFont(display, *gc, font_info->fid);

	/* specify black foreground since default window background is 
	 * white and default foreground is undefined. */
	XSetForeground(display, *gc, BlackPixel(display,screen_num));

	/* set line attributes */
	XSetLineAttributes(display, *gc, line_width, line_style, 
			cap_style, join_style);

	/* set dashes */
	XSetDashes(display, *gc, dash_offset, dash_list, list_length);
}

static void load_font(XFontStruct **font_info)
{
	char *fontname = "9x15";

	/* Load font and get font information structure. */
	if ((*font_info = XLoadQueryFont(display,fontname)) == NULL)
	{
		(void) fprintf( stderr, "Err! Cannot open 9x15 font\n");
		exit( -1 );
	}
}

static void draw_text(Window win, GC gc, XFontStruct * font_info, unsigned int win_width, unsigned int win_height)
{
	char *string = "Hello, the World!";
	int len;
	int width;
	char cd_height[50], cd_width[50], cd_depth[50];
	int font_height;
	int initial_y_offset, x_offset;


	/* need length for both XTextWidth and XDrawString */
	len = strlen(string);

	/* get string widths for centering */
	width = XTextWidth(font_info, string, len);

	font_height = font_info->ascent + font_info->descent;

	/* output text, centered on each line */
	XDrawString(display, win, gc, (win_width - width)/2, 
			(win_height - font_height)/2,
			string, len);
}

void draw_graphics(Window win, GC gc, unsigned int x, unsigned int y,  unsigned int width, unsigned int height)
{
	XDrawRectangle(display, win, gc, x, y, width, height);
}

void TooSmall(Window win, GC gc, XFontStruct *font_info)
{
	char *string1 = "Too Small";
	int y_offset, x_offset;

	y_offset = font_info->ascent + 2;
	x_offset = 2;

	/* output text, centered on each line */
	XDrawString(display, win, gc, x_offset, y_offset, string1, 
			strlen(string1));
}


int main(int argc, char* argv[])
{
    Window win;
	unsigned int width, height;
	int window_size = BIG_ENOUGH;
    char* display_name;
    Pixmap icon_pixmap;
	char *window_name = "Hello World";
    char *icon_name="helloworld";
    XTextProperty windowName;
    XTextProperty iconName;
	XSizeHints *size_hints;
	XWMHints *wm_hints;
	XClassHint *class_hints;
	XFontStruct *font_info;
	GC gc;
	XEvent evt;


    //#. connect to X server

    display = XOpenDisplay(NULL);
    if(!display)
    {
        fprintf(stderr, "Err, cannot connect to the x server %s", XDisplayName(display_name));
        exit(-1);
    }

    //#. create simple window
    screen_num = DefaultScreen(display);
    win = XCreateSimpleWindow(display, RootWindow(display, screen_num)
                                , 0, 0                                          //x,y
                                , 800, 600                                      //width, height
                                , 4                                             //border width
                                , BlackPixel(display, screen_num)               //border color
                                , WhitePixel(display,screen_num)                //background color
                                );

    //#. set window hints

    //##. load icon pixmap
    icon_pixmap = XCreateBitmapFromData(display, win, helloicon_bits, helloicon_width, helloicon_height);

    //##.
    size_hints = XAllocSizeHints();
    assert(size_hints);
	size_hints->flags = PPosition | PSize | PMinSize;
	size_hints->min_width = 300;
	size_hints->min_height = 200;

    //##.
    XStringListToTextProperty(&window_name, 1, &windowName);
    XStringListToTextProperty(&icon_name, 1, &iconName);

    //##.
    wm_hints = XAllocWMHints();
    assert(wm_hints);
    wm_hints->initial_state = NormalState;
	wm_hints->input = True;
	wm_hints->icon_pixmap = icon_pixmap;
	wm_hints->flags = StateHint | IconPixmapHint | InputHint;

    //##.
    class_hints = XAllocClassHint();
    assert(class_hints);
	class_hints->res_name = argv[0];
	class_hints->res_class = "Basicwin";

	XSetWMProperties(display, win, &windowName, &iconName, 
			argv, argc, size_hints, wm_hints, 
			class_hints);

    //#.event mask

	XSelectInput(display, win, ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask);


	//#. create GC for text and drawing */
	load_font(&font_info);
	getGC(win, &gc, font_info);

	//#. Display window
	XMapWindow(display, win);

    //#. event loop
    while(1)
    {
		XNextEvent(display, &evt);
		switch  (evt.type) {
		case Expose:
			//unless this is the last contiguous expose, don't draw the window
			if (evt.xexpose.count != 0)
				break;

			if (window_size == TOO_SMALL)
				TooSmall(win, gc, font_info);
			else {
				draw_text(win, gc, font_info, width, height);
				draw_graphics(win, gc, 100, 100, 200, 100);
			}
			break;
		case ConfigureNotify:
			//window has been resized, change width and height to send to draw_text and draw_graphics in next Expose
			width = evt.xconfigure.width;
			height = evt.xconfigure.height;
			if ((width < size_hints->min_width) || 
					(height < size_hints->min_height))
				window_size = TOO_SMALL;
			else
				window_size = BIG_ENOUGH;
			break;            
		case ButtonPress:
			/* trickle down into KeyPress (no break) */
		case KeyPress:
			XUnloadFont(display, font_info->fid);
			XFreeGC(display, gc);
			XCloseDisplay(display);
			exit(1);
		default:
			/* all events selected by StructureNotifyMask
			 * except ConfigureNotify are thrown away here,
			 * since nothing is done with them */
			break;
		} /* end switch */

    }

    return 0;
}